package com.flash.javamate.handler.impl;

import com.flash.javamate.entity.AbstractDataModel;
import com.flash.javamate.entity.ApiDocDataModel;
import com.flash.javamate.entity.ApiDocEnvironment;
import com.flash.javamate.handler.DataModelHandler;
import com.flash.javamate.utils.JavaPathHandler;
import com.github.javaparser.JavaParser;
import com.github.javaparser.ParseException;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.ImportDeclaration;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.AnnotableNode;
import com.github.javaparser.ast.body.BodyDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.body.TypeDeclaration;
import com.github.javaparser.ast.comments.Comment;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.MarkerAnnotationExpr;
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
import com.github.javaparser.ast.expr.StringLiteralExpr;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.type.ReferenceType;
import com.github.javaparser.ast.type.Type;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author sunyiming
 * @date 2018/8/13
 */
public class ControllerDataModelHandler implements DataModelHandler {

    private ApiDocEnvironment env;

    public ControllerDataModelHandler(ApiDocEnvironment hostEnvironment) {
        this.env = hostEnvironment;
    }

    @Override
    public ApiDocDataModel handle(CompilationUnit unit) throws IOException, ParseException {
        ApiDocDataModel apiDocDataModel = new ApiDocDataModel();
        TypeDeclaration theClazz = unit.getTypes().get(0);
        List<ImportDeclaration> imports = unit.getImports();

        // 类信息
        apiDocDataModel.setName(theClazz.getName());
        setComment(getComment(theClazz), apiDocDataModel);

        // 找到mapping信息
        Map<String, Object> clazzMappingMap = getMappingAnnotation(theClazz);
        if (clazzMappingMap == null) {
            return null;
        }
        String clazzUrl = clazzMappingMap.get("value").toString();

        // 方法信息
        List<BodyDeclaration> members = theClazz.getMembers();
        List<ApiDocDataModel.MethodDataModel> methods = new ArrayList<>();
        for (BodyDeclaration member : members) {
            if (member instanceof MethodDeclaration) {
                // 方法信息封装
                MethodDeclaration methodMember = (MethodDeclaration) member;
                // 判断是否包含@mapping注解，若包含则提取出来
                Map<String, Object> mappingMap = getMappingAnnotation(methodMember);
                if (mappingMap == null) {
                    continue;
                }
                ApiDocDataModel.MethodDataModel methodDataModel = new ApiDocDataModel().new MethodDataModel();
                // 设置名字、注释和访问方式
                methodDataModel.setName(methodMember.getName());
                setComment(getComment(member), methodDataModel);
                methodDataModel.setMethod(mappingMap.get("name").toString());
                if (((MethodDeclaration) member).getName().equals("uploadFile")) {
                    methodDataModel.setContentType("multipart/form-data");
                } else {
                    methodDataModel.setContentType("application/json");
                }
                // 设置访问路径
                methodDataModel.setDevUrl(env.getDevHost() + clazzUrl + mappingMap.get("value").toString());
                methodDataModel.setTestUrl(env.getTestHost() + clazzUrl + mappingMap.get("value").toString());
                methodDataModel.setProdUrl(env.getProdHost() + clazzUrl + mappingMap.get("value").toString());

                // 设置param
                List<ApiDocDataModel.ParamDataModel> params = new ArrayList<>();
                List<Parameter> parameters = methodMember.getParameters();
                for (Parameter parameter : parameters) {
                    // 封装每一个param参数
                    String paramFullName = findParameterFromImports(parameter, imports);
                    try {
                        CompilationUnit paramUnit = JavaParser.parse(new File(JavaPathHandler.getRealPath(env.getUserDir(), paramFullName)));
                        parseUnit2ParamDataModel(paramUnit, params);
                    } catch (FileNotFoundException e) {
                        System.out.println(e.getMessage());
                    }
                }

                methodDataModel.setParams(params);

                // 设置return
                List<ApiDocDataModel.ParamDataModel> returnParams = new ArrayList<>();
                ApiDocDataModel.ParamDataModel returnParamDataModel = new ApiDocDataModel().new ParamDataModel();
                Type returnType = methodMember.getType();
                while (true) {
                    List<Type> returnTypeArgs = getTypeArgs(returnType);
                    if (returnTypeArgs == null || returnTypeArgs.size() == 0) {
                        break;
                    }
                    for (Type returnTypeArg : returnTypeArgs) {
                        String returnTypeParamFullname = findTypeFromImports(returnTypeArg, imports);

                        // 将data的T变了
                        for (ApiDocDataModel.ParamDataModel returnParam : returnParams) {
                            if ("data".equals(returnParam.getName())) {
                                returnParam.setType(returnTypeParamFullname.substring(returnTypeParamFullname.lastIndexOf(".") + 1));
                            }
                        }

                        try {
                            File file = JavaPathHandler.getFile(env.getUserDir(), returnTypeParamFullname, env.getUserDirs());
                            CompilationUnit paramUnit = JavaParser.parse(file);
                            parseUnit2ParamDataModel(paramUnit, returnParams);
                        } catch (FileNotFoundException e) {
                            System.out.println(e.getMessage());
                        }
                    }
                    returnType = returnTypeArgs.get(0);
                }


                methodDataModel.setReturnParams(returnParams);

                // 添加此method
                methods.add(methodDataModel);
            }
        }

        apiDocDataModel.setMethods(methods);

        return apiDocDataModel;
    }

    private List<Type> getTypeArgs(Type returnType) {
        String paramTypeName = null;
        if (returnType instanceof ReferenceType) {
            Type type1 = ((ReferenceType) returnType).getType();
            if (type1 instanceof ClassOrInterfaceType) {
                return ((ClassOrInterfaceType) type1).getTypeArgs();
            }
        }
        return null;
    }

    private String findParameterFromImports(Parameter parameter, List<ImportDeclaration> imports) {
        Type type = parameter.getType();
        return findTypeFromImports(type, imports);
    }

    private String findTypeFromImports(Type type, List<ImportDeclaration> imports) {
        String paramTypeName = null;
        if (type instanceof ReferenceType) {
            Type type1 = ((ReferenceType) type).getType();
            if (type1 instanceof ClassOrInterfaceType) {
                paramTypeName = ((ClassOrInterfaceType) type1).getName();
            }
        }
        return findNameFromImports(paramTypeName, imports);
    }

    private String findNameFromImports(String name, List<ImportDeclaration> imports) {
        for (ImportDeclaration anImport : imports) {
            NameExpr nameExpr = anImport.getName();
            String simpleName = nameExpr.getName();
            String fullName = nameExpr.toString();
            if (simpleName.equals(name)) {
                return fullName;
            }
        }
        return name;
    }

    private void parseUnit2ParamDataModel(CompilationUnit paramUnit, List<ApiDocDataModel.ParamDataModel> params) {
        List<TypeDeclaration> types = paramUnit.getTypes();
        for (TypeDeclaration type : types) {
            List<BodyDeclaration> members = type.getMembers();
            for (BodyDeclaration member : members) {
                if (member instanceof FieldDeclaration) {
                    ApiDocDataModel.ParamDataModel paramDataModel = new ApiDocDataModel().new ParamDataModel();
                    paramDataModel.setName(((FieldDeclaration) member).getVariables().get(0).getId().getName());
                    paramDataModel.setType(((FieldDeclaration) member).getType().toString());
                    setComment(getComment(member), paramDataModel);
                    params.add(paramDataModel);
                }
            }
        }
    }

    private void setComment(String comment, AbstractDataModel dataModel) {
        comment = comment.trim();
        comment = comment.replace("*", "<br>").trim();
        if (comment.startsWith("<br>")) {
            comment = comment.substring("<br>".length()).trim();
        }
        if (comment.contains(" ")) {
            dataModel.setComment(comment.substring(0, comment.indexOf(" ")));
            dataModel.setDetail(comment.substring(comment.indexOf(" ")).replaceFirst("<br>", ""));
        } else {
            dataModel.setComment(comment);
            dataModel.setDetail("无");
        }
    }

    /**
     * 查找节点的注释
     */
    private Map<String, Object> getMappingAnnotation(AnnotableNode annotableNode) {
        List<AnnotationExpr> annotations = annotableNode.getAnnotations();
        Map<String, Object> map = new HashMap<>();
        for (AnnotationExpr annotation : annotations) {
            String name = annotation.getName().getName();
            if ("RequestMapping".equals(name)) {
                map.put("name", "get/post");
                return addMappingValue(map, annotation);
            }
            if ("GetMapping".equals(name)) {
                map.put("name", "get");
                return addMappingValue(map, annotation);
            }
            if ("PostMapping".equals(name)) {
                map.put("name", "post");
                return addMappingValue(map, annotation);
            }
        }
        return null;
    }

    private Map<String, Object> addMappingValue(Map<String, Object> map, AnnotationExpr annotation) {
        if (annotation instanceof MarkerAnnotationExpr) {
            map.put("value", "");
        } else if (annotation instanceof SingleMemberAnnotationExpr) {
            Expression memberValue = ((SingleMemberAnnotationExpr) annotation).getMemberValue();
            if (memberValue instanceof StringLiteralExpr) {
                map.put("value", ((StringLiteralExpr) memberValue).getValue());
            } else {
                map.put("value", "UNKNOWN");
            }
        } else {
            map.put("value", "UNKNOWN");
        }
        return map;
    }

    public static String getComment(Node node) {
        if (node == null) {
            return "暂无节点";
        }
        Comment comment = node.getComment();
        if (comment == null) {
            return "暂无说明";
        }
        return comment.getContent();
    }
}
